// Copyright 2023 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import x264_encoder from '../3rd/video-codec-lib/X264Encoder';
import Util from '../Util';

class VideoEncode {
    constructor() {
        this.util = new Util();
        this.module = null;
        this.h264_error = this.openEncoderError;
        this._state = 'unconfigured';
        this.videoH264DataList = [];
    }

    get encodeQueueSize() {
        console.log('unconfigured');
    }

    get state() {
        return this._state;
    }

    getWasm(options) {
        this.util.loadWasm(`${options.x264WasmPath}/X264Encoder.wasm`).then(wasmBinary => x264_encoder({
            instantiateWasm: (imports, successCallback) => {
                imports.env.h264_write_polyfill = (ptr, number) => {
                    this.getH264Data({
                        copyTo: (dest) => {
                            const len = dest.length;
                            for(let i=0; i<len; i++) {
                                dest[i] = this.module.HEAPU8[ptr + i];
                            }
                        },
                        byteLength: number
                    }, {});
                };
                WebAssembly.instantiate(new Uint8Array(wasmBinary), imports).then((output) => {
                    successCallback(output.instance);
                });
            }
        })).then(module => {
            this.module = module;
            this.encoder = new this.module.X264Encoder();
            this.encoder.configure(options.width, options.height);
            this._state = 'configured';

            self.postMessage({
               type: 'startEncode' 
            }, []);
        });
    }

    encode(frame) {
        if(this._state === 'unconfigured') {
            throw `Failed to execute 'encode' on 'VideoEncoder': Cannot call 'encode' on an unconfigured codec.`;
        }
        if(!frame) {
            throw ''
        }

        const yuv = this.util.convertImageDataToYUV420(frame);
        const yuvPtr = this.module._malloc(yuv.byteLength);
        this.module.HEAPU8.set(yuv, yuvPtr);
        this.encoder.encode(yuvPtr);
        this.module._free(yuvPtr);
    }

    openEncoderError(e) {
        console.log(e.message);
    }

    getH264Data(chunk, opts, format='annexb') {
        let chunkData = new Uint8Array(chunk.byteLength);
        chunk.copyTo(chunkData);
        this.videoH264DataList.push(chunkData);

        if (this.videoH264DataList.length) {
            const itemH264Data = this.videoH264DataList.shift();
            self.postMessage({
                type: 'encodeResult',
                data: itemH264Data
            }, [itemH264Data.buffer]);
        }
    }
}

export default VideoEncode;